/**
 * @file button_monitor.c
 * @author monster
 * @brief
 * @version 0.1
 * @date 2024-04-28
 *
 * @copyright Copyright (c) 2024
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "button_monitor.h"
#include <string.h>

/*********************
 *      DEFINES
 *********************/

//#define GET_TICK() millis()

#ifndef UINT32_MAX
#define UINT32_MAX 0xffffffffU  /* 4294967295U */
#endif


/**********************
 *      TYPEDEFS
 **********************/
/**
 * Descriptor of a button linked list
 */
typedef struct {
	button_t* head;
	button_t* tail;
	uint16_t  size;
} button_ll_t;


/**********************
 *  STATIC VARIABLES
 **********************/

/* Button object static memory buffer */
#if !BM_DYNAMIC_MEMORY
static button_t ButtonContainer[BM_BUTTON_SIZE];
#endif

static button_ll_t button_ll;


/**********************
 *      MACROS
 **********************/
#ifndef GET_TICK
#warning "Please define the GET_TICK() function to get the system time."
static get_tick_t GET_TICK = NULL;
#endif

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

/**
 * @brief Get button pointer
 * @param idx Button index in the linked list
 * @return If button is exist return button_t pointer
 */
button_t* get_ll_button(int idx) {
	/* if next index is after the last index,
	 * this will improve query speed */
	static int       last_idx = 0;
	static button_t* last_btn = NULL;


	if ((idx < 0) || (idx > button_ll.size - 1)) {
		return NULL;
	}

	last_idx = idx;

	button_t* btn = button_ll.head;

	if ((last_btn != NULL) && (idx >= last_idx)) {
		btn = last_btn;
		idx = idx - last_idx;
	}

	while (idx) {
		btn = btn->next;
		idx--;
	}

	last_btn = btn;
	return btn;
}

/**
 * @brief Check button is exist
 * @param id to a button_t pointer
 * @return If button exist return the index in the linked list, else return -1.
 */
int button_is_exist(button_t* id) {
	if (button_ll.size == 0)
		return -1; //!< Button Not create

	int       index = 0;
	button_t* btn   = button_ll.head;

	while (btn) {
		if (btn == id) {
			return index;
		}

		index++;
		btn = btn->next;
	}

	/* Not find the button */
	return -1;
}

/**
 * 获取与上次时间的时间差
 * @param prevTick 上次的时间戳
 * @retval 时间差
 */
static uint32_t GetTickElapse(uint32_t prevTick) {
	uint32_t actTime = GET_TICK();

	if (actTime >= prevTick) {
		prevTick = actTime - prevTick;
	}
	else {
		prevTick = UINT32_MAX - prevTick + 1;
		prevTick += actTime;
	}

	return prevTick;
}


/**
 * @brief Set get tick function callback
 * @param tick Pointer to get_tick_t
 */
void button_set_tick(get_tick_t tick) {
	if (tick != NULL) {
		GET_TICK = tick;
	}
}

/**
 * @brief Create button object
 * @param name Button name
 * @param get_press pointer to button press read callback
 * @param callback pointer to event callback
 * @param long_press_time
 * @param long_press_time_repeat
 * @param double_click_time
 * @return
 */
button_t* button_create(char*    name, button_press_t      get_press, button_callback_t     callback,
						uint16_t long_press_time, uint16_t long_press_time_repeat, uint16_t double_click_time) {
	if (name == NULL || get_press == NULL) {
		return NULL;
	}

	button_t* button = NULL;

#if BM_DYNAMIC_MEMORY
	button = (button_t*)malloc(sizeof(button_t));
	if (button == NULL) {
		return NULL;
	}
#else
	for (uint16_t i = 0; i < BM_BUTTON_SIZE; i++) {
		if (ButtonContainer[i].name == NULL) {
			button = &ButtonContainer[i];
			break;
		}
	}
#endif
	button->name      = name;
	button->get_press = get_press;
	button->callback  = callback;
	button->nowState  = STATE_NO_PRESS;
	memset(&button->record, 0, sizeof(button_record_t));

	button->priv.longPressTimeCfg       = long_press_time;
	button->priv.longPressRepeatTimeCfg = long_press_time_repeat;
	button->priv.doubleClickTimeCfg     = double_click_time;
	button->priv.lastLongPressTime      = 0;
	button->priv.lastPressTime          = 0;
	button->priv.lastClickTime          = 0;
	button->priv.isLongPressed          = false;
	button->next                        = NULL;

	if (button_ll.tail != NULL) {
		button_ll.tail->next = button;
	}

	if (button_ll.head == NULL) {
		button_ll.head = button;
	}

	button_ll.tail = button;
	button_ll.size++;

	return button;
}

/**
 * @brief Remove button
 * @param id to a button_t pointer
 */
void button_remove(button_t* id) {
	int index = button_is_exist(id);
	if (index < 0) {
		return;
	}

	/* Remove button is linked list head */
	if (id == button_ll.head) {
		button_ll.head = id->next;
		if (button_ll.head == NULL) {
			button_ll.tail = NULL;
		}
	}
	else if (id == button_ll.tail) {
		button_ll.tail = get_ll_button(index - 1);
		if (button_ll.tail == NULL) {
			button_ll.head = NULL;
		}
	}
	else {
		get_ll_button(index - 1)->next = id->next;
	}

#if BM_DYNAMIC_MEMORY
	free(id);
#else
	memset(id, 0, sizeof(button_t));
#endif

	button_ll.size--;
}

/**
 * @brief Get button press record
 * @param btn to a button_t pointer
 * @return If button is exist return record, else return NULL.
 */
button_record_t button_get_record(button_t* btn) {
	button_record_t record = {0};

	if (btn == NULL) {
		return record;
	}

	memcpy(&record, &btn->record, sizeof(button_record_t));
	memset(&btn->record, 0, sizeof(button_record_t));

	return record;
}

/**
 * @brief Monitor once button
 * @param btn to a button_t pointer
 */
void button_monitor(button_t* btn) {
	if (!GET_TICK) {
		return;
	}

	bool isPress = btn->get_press(btn->name);

	if (isPress && btn->nowState == STATE_NO_PRESS) {
		btn->nowState = STATE_PRESS;

		btn->record.pressed     = true;
		btn->priv.lastPressTime = GET_TICK();

		btn->callback(btn, EVENT_PRESSED);
		btn->callback(btn, EVENT_CHANGED);
	}

	/* Button not press, nothing to do */
	if (btn->nowState == STATE_NO_PRESS) {
		return;
	}

	if (isPress) {
		btn->callback(btn, EVENT_PRESSING);
	}

	if (isPress && GetTickElapse(btn->priv.lastPressTime) >= btn->priv.longPressTimeCfg) {
		btn->nowState = STATE_LONG_PRESS;

		/* long press */
		if (!btn->priv.isLongPressed) {
			btn->callback(btn, EVENT_LONG_PRESSED);
			btn->priv.lastLongPressTime = GET_TICK();
			btn->record.longPressed     = btn->priv.isLongPressed = true;
		}
		else if (GetTickElapse(btn->priv.lastLongPressTime) >= btn->priv.longPressRepeatTimeCfg) {
			btn->callback(btn, EVENT_LONG_PRESSED_REPEAT);
			btn->priv.lastLongPressTime = GET_TICK();
		}
	}
	else if (!isPress) {
		// release check
		btn->nowState = STATE_NO_PRESS;
		btn->record.clickCnt++;

		/* double click */
		if (GetTickElapse(btn->priv.lastClickTime) < btn->priv.doubleClickTimeCfg) {
			// btn->record.clickCnt++;
			btn->callback(btn, EVENT_DOUBLE_CLICKED);
			return;
		}

		/* long press released */
		if (btn->priv.isLongPressed) {
			btn->callback(btn, EVENT_LONG_PRESSED_RELEASED);
		}

		/* short click */
		if (GetTickElapse(btn->priv.lastPressTime) < btn->priv.longPressTimeCfg) {
			btn->record.shortClicked = true;
			btn->callback(btn, EVENT_SHORT_CLICKED);
		}

		btn->priv.isLongPressed = false;
		btn->priv.lastClickTime = GET_TICK();

		btn->record.clicked = true;
		btn->record.pressed = false;

		btn->callback(btn, EVENT_CLICKED);
		btn->callback(btn, EVENT_RELEASED);
		btn->callback(btn, EVENT_CHANGED);
	}
}

/**
 * @brief handler all button event
 * @return None
 */
void button_handler(void) {
	if (button_ll.size == 0) {
		return;
	}

	button_t* btn = button_ll.head;

	while (btn) {
		button_monitor(btn);
		btn = btn->next;
	}
}
